/* Copyright 203-2115 AtomWare Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * The flowing information must be keep 
 * ChatBusinessService.java
 * netty-socket-io
 * 2013年9月9日上午9:19:29
 * @author 屈甲康
 * QQ285799123
 * mailto:qujiakang@gmail.com
 */

package org.jackysoft.service.impl;

import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jackysoft.entity.Department;
import org.jackysoft.entity.SecurityRole;
import org.jackysoft.entity.User;
import org.jackysoft.entity.nosql.ChatMessage;
import org.jackysoft.entity.nosql.LoginMessage;
import org.jackysoft.entity.nosql.NameValuePair;
import org.jackysoft.entity.nosql.NotificationMessage;
import org.jackysoft.entity.nosql.PrivateChatPair;
import org.jackysoft.entity.nosql.PrivateMessage;
import org.jackysoft.entity.nosql.PublicMessage;
import org.jackysoft.entity.nosql.RemindEvent;
import org.jackysoft.entity.nosql.UserIO;
import org.jackysoft.entity.nosql.UserInfo;
import org.jackysoft.nosql.LabelColors;
import org.jackysoft.nosql.MessageWhile;
import org.jackysoft.nosql.UserEvent;
import org.jackysoft.query.QueryBuilder;
import org.jackysoft.query.RunnerQuery;
import org.jackysoft.service.ChatMessageService;
import org.jackysoft.service.DepartmentService;
import org.jackysoft.service.RemindEventService;
import org.jackysoft.service.UserService;
import org.jackysoft.ztree.DepartmentZtreeFunction;
import org.jackysoft.ztree.OnlineUserZtreeFunction;
import org.jackysoft.ztree.TreeIdInfo;
import org.jackysoft.ztree.TreeInfo;
import org.jackysoft.ztree.ZTreeNode;
import org.springframework.dao.DataAccessException;

import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.BroadcastOperations;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class UserNotificationService {
	/**
	 * 
	 */
	final static Log logger = LogFactory.getLog(UserNotificationService.class);
	final static OnlineUserManager userManager = new OnlineUserManager();
	final static Map<String, UserInfo> userCache = Maps.newHashMap();
	final static Map<String, Collection<ZTreeNode>> ztreeCache = Maps
			.newHashMap();
	private @Resource
	UserService userService;
	private @Resource
	RemindEventService remindService;
	private @Resource
	ChatMessageService chatervice;
	@Resource
	private DepartmentService deptService;
	private SocketIOServer server;

	public UserNotificationService() {
	}

	@OnEvent(UserEvent.LoginEvent)
	public void onloginEventHandler(SocketIOClient client, User data,
			AckRequest ackRequest) {
		logger.info(client.getSessionId() + " has login");

		if (data == null || StringUtils.isEmpty(data.getUsername())) {
			client.sendEvent(UserEvent.LoginFail, "1");
			return;
		}

		if (userManager.hasLogin(data)) {
			client.sendEvent(UserEvent.LoginFail, "3");
			return;
		}

		User user = userService.getLoginInfo(data.getUsername());
	
		if (user.getPassword().equals(data.getPassword())) {

			userManager.put(client, user);
						
			RunnerQuery query = QueryBuilder.build();
			query.addField("receiver", user.getUsername());
			query.addField("valid", "true");
			query.addOrder("datetime","desc");
			Collection<RemindEvent> msgs = remindService.queryAll(query);

			
			LoginMessage<RemindEvent> lmsg = new LoginMessage<RemindEvent>(
					user.getUsername(), user.getLocalName(), user.getPassword(),
					user.getRole().getName(), user.getDepartment().getName());
			lmsg.setMessageList(msgs);
			client.sendEvent(UserEvent.LoginSuccess, lmsg);

		} else {
			client.sendEvent(UserEvent.LoginFail, "0");
		}

	}

	@OnEvent(UserEvent.UserJoin)
	public void onUserJoinHandler(SocketIOClient client, String userId,
			AckRequest ackRequest) {
		User user = userService.getLoginInfo(userId);
		BroadcastOperations bo = server.getBroadcastOperations();
		UserIO uio = new UserIO();
		uio.setArgs(user.getDepartment().getName(), user.getLocalName());
		updateOnlines(uio);
		bo.sendEvent(UserEvent.UserJoin, uio);
		userManager.joinPublicRoom(userId);

	}

	@OnEvent(UserEvent.WelcomeEvent)
	public void onWelcomeHandler(SocketIOClient client, User data,
			AckRequest ackRequest) {
		User user = userService.getLoginInfo(data.getUsername());
		RunnerQuery query = QueryBuilder.build();
		query.addField("receiver", user.getUsername());
		query.addField("valid", "true");
		query.addOrder("datetime","desc");
		Collection<RemindEvent> msgs = remindService.queryAll(query);
		LoginMessage<RemindEvent> lmsg = new LoginMessage<RemindEvent>(
				user.getUsername(), user.getLocalName(), user.getPassword(), user
						.getRole().getName(), user.getDepartment().getName());
		lmsg.setMessageList(msgs);
		BroadcastOperations bo = server.getBroadcastOperations();
		bo.sendEvent(UserEvent.WelcomeEvent, lmsg);

	}

	@OnEvent(UserEvent.LogoutEvent)
	public void onlogoutEventHandler(SocketIOClient client, User data,
			AckRequest ackRequest) {

		User user = userManager.get(client);
		if (user != null)
			logger.info(user.getUsername() + " has logout");
		userQuit(client);
	}

	private void userQuit(SocketIOClient client) {
		User user = userManager.get(client);
		if (user == null)
			return;
		userManager.remove(client);
		String pattern = "<span class='label label-info'>系统消息:</span>%s的<b>%s</b> 下线了!";
		String[] args = { user.getDepartment().getName(), user.getLocalName() };
		UserIO uio = new UserIO();
		uio.setPattern(pattern);
		uio.setArgs(args);
		client.sendEvent(UserEvent.LogoutEvent, uio);
		logger.info("logout event is sent for  " + user.getUsername());
		userManager.removeChat(user.getUsername());
		updateOnlines(uio);
	}

	@OnEvent(UserEvent.IgnoreNotify)
	public void onIgnoreNotifyEventHandler(SocketIOClient client, String data,
			AckRequest ackRequest) {
		String tdata = StringUtils.trimToEmpty(data);
		if (StringUtils.isEmpty(tdata))
			return;
		String[] idz = tdata.split(",");
		logger.info(Arrays.toString(idz) + " will be disabled ");
		remindService.changeValid(false, idz);

	}

	@OnEvent(UserEvent.SayHiEvent)
	public void onsayHiEventHandler(SocketIOClient client, String data,
			AckRequest ackRequest) {
		logger.info(client.getSessionId() + " say hi event");
		BroadcastOperations bo = server.getBroadcastOperations();	
		bo.sendEvent(UserEvent.SayHiEvent, data);
	}

	@OnEvent(UserEvent.Notified)
	public void onNotifiedEventHandler(SocketIOClient client, String data,
			AckRequest ackRequest) {

		logger.info("notified message was received:"+data);
		Gson gson = new Gson();
		Type listType = new TypeToken<Map<String, Collection<NotificationMessage>>>() {
		}.getType();
		Map<String, Collection<NotificationMessage>> t = gson.fromJson(data,
				listType);
		
		for(String user:t.keySet()){
			SocketIOClient sc = userManager.clientOf(user);
			if(sc==null)continue;
			threadWait();
			Collection<NotificationMessage> msgs = t.get(user);
			sc.sendEvent(UserEvent.Notified, msgs);			
			logger.info("notified event emited data to  "+user);
		}
		
	}

	@OnConnect
	public void onConnectHandler(SocketIOClient client) {
		logger.info(client.getRemoteAddress() + " is connected!");
	}

	@OnDisconnect
	public void onDisconnectHandler(SocketIOClient client) {
		userQuit(client);
	}


	public void setServer(SocketIOServer server) {
		this.server = server;
	}

	public void threadWait(){
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			logger.error(e);
		}
	}
	
	// only data object is required in arguments,
	// SocketIOClient and AckRequest could be ommited

	/**
	 * @param client
	 * @param data
	 * @param ackRequest
	 */
	@OnEvent(UserEvent.Message)
	public void onMessageEventHandler(SocketIOClient client, String data,
			AckRequest ackRequest) {

		User user = userManager.get(client);
		ChatMessage msg = new ChatMessage();
		msg.setSender(getUserInfo(user.getUsername()));
		msg.setReceiver(UserInfo.PUBLIC);
		msg.setArgs(LabelColors.labelColor(), user.getLocalName(),
				user.getUsername(), MessageWhile.messageTime(), data);
		msg.setDatetime(MessageWhile.messageMills());
		BroadcastOperations bo = server.getBroadcastOperations();
		logger.info(msg.getContent());
		bo.sendEvent(UserEvent.Message, msg);
		chatervice.save(msg);

	}

	@OnEvent(UserEvent.PrivateMessage)
	public void onPrivateMessageHandler(SocketIOClient client,
			PrivateMessage data, AckRequest ackRequest) {

		UserInfo sender = getUserInfo(data.getSender().getName());
		UserInfo receiver = getUserInfo(data.getReceiver().getName());
		ChatMessage msg = new ChatMessage();
		msg.setSender(sender);
		msg.setReceiver(receiver);
		msg.setArgs(LabelColors.labelColor(), sender.getLocalName(),
				sender.getName(), MessageWhile.messageTime(), data.getArgs()[0]);
		msg.setDatetime(MessageWhile.messageMills());

		SocketIOClient rclient = userManager.clientOf(receiver.getName());
		if (rclient != null) {
			rclient.sendEvent(UserEvent.PrivateMessage, msg);
		}

		SocketIOClient sclient = userManager.clientOf(sender.getName());
		if (sclient != null) {
			sclient.sendEvent(UserEvent.PrivateMessage, msg);
		}
		chatervice.save(msg);
	}
	
	@OnEvent(UserEvent.EndPrivateSession)
	public void onEndPrivateSessionHandler(SocketIOClient client,
			PrivateMessage data, AckRequest ackRequest) {
		client.sendEvent(UserEvent.EndPrivateSession, data);
		String receiver = data.getReceiver().getName();
	    if(receiver!=null){
	    	SocketIOClient rclient = userManager.clientOf(receiver);
	    	PrivateChatPair rdata = new PrivateChatPair();
	    	rdata.setReceiver(new UserInfo(data.getSender()));
	    	rdata.setSender(new UserInfo(data.getReceiver()));	    	
	        rclient.sendEvent(UserEvent.EndPrivateSession, rdata);
	    }
		userManager.removeChat(data.getSender().getName());
	}
	

	@OnEvent(UserEvent.PubMessage)
	public void onPubMessageHandler(SocketIOClient client, String data,
			AckRequest ackRequest) {

		Gson gson = new Gson();
		Type listType = new TypeToken<PublicMessage>() {
		}.getType();
		PublicMessage bean = gson.fromJson(data, listType);
		BroadcastOperations bo = server.getBroadcastOperations();
		bo.sendEvent(UserEvent.PubMessage, bean);
		logger.info("public messages were  sent");
	}

	@OnEvent(UserEvent.RequesteChat)
	public void onReqChatHandler(SocketIOClient client, PrivateChatPair data,
			AckRequest ackRequest) {
		String receiver = data.getReceiver().getName();
		SocketIOClient skc = userManager.clientOf(receiver);

		PrivateChatPair result = new PrivateChatPair();
		UserInfo user = getUserInfo(data.getSender().getName());
		result.setSender(user);
		user = getUserInfo(data.getReceiver().getName());
		result.setReceiver(user);
		boolean hasChat = userManager.addChat(result);
		if (!hasChat) {
			if(skc==null)
				client.sendEvent(UserEvent.ReqChatFailure, result);
			else
				skc.sendEvent(UserEvent.RequesteChat, result);
		} 

	}

	@OnEvent(UserEvent.ReqAccept)
	public void onReqChatAccept(SocketIOClient client, PrivateChatPair data,
			AckRequest ackRequest) {
		SocketIOClient sclient = userManager.clientOf(data.getSender()
				.getName());
		if (sclient == null) {
			client.sendEvent(UserEvent.ReqChatFailure,String.format("用户%s已经离线", data.getSender()
					.getLocalName()));
			return;
		} else {
			PrivateChatPair senderData = new PrivateChatPair();
			senderData.setSender(getUserInfo(data.getSender().getName()));
			senderData.setReceiver(getUserInfo(data.getReceiver().getName()));

			PrivateChatPair receiverData = new PrivateChatPair();
			receiverData.setSender(getUserInfo(data.getReceiver().getName()));
			receiverData.setReceiver(getUserInfo(data.getSender().getName()));
			client.sendEvent(UserEvent.ReqAccept, receiverData);
			sclient.sendEvent(UserEvent.ReqAccept, senderData);

			userManager.addChat(senderData);
		}

	}

	@OnEvent(UserEvent.ReqReject)
	public void onReqChatReject(SocketIOClient client, PrivateChatPair data,
			AckRequest ackRequest) {
		SocketIOClient sclient = userManager.clientOf(data.getSender()
				.getName());
		if (sclient == null) {
			client.sendEvent(UserEvent.ReqChatFailure,String.format("用户%s已经离线", data.getSender()
					.getLocalName()));
			return;
		} else {
			PrivateChatPair adata = new PrivateChatPair();
			adata.setSender(getUserInfo(data.getSender().getName()));
			adata.setReceiver(getUserInfo(data.getReceiver().getName()));
			client.sendEvent(UserEvent.ReqReject, adata);
			sclient.sendEvent(UserEvent.ReqReject, adata);
		}
	}
	
	@OnEvent(UserEvent.NewRemind)
	public void onNewRemindHandler(SocketIOClient client, 
			String data,
			AckRequest ackRequest){
		logger.info(data);
		Gson gson = new Gson();
		Type listType = new TypeToken<Collection<RemindEvent>>() {
		}.getType();
		Collection<RemindEvent> beans = gson.fromJson(data, listType);
		remindService.save(beans);
		logger.info("remind events was created !" + beans);
	}
	
	@OnEvent(UserEvent.RemoveRemind)
	public void onRemoveRemindHandler(SocketIOClient client, 
			String id,
			AckRequest ackRequest){
		 remindService.changeValid(false, id);
	}
	
	@OnEvent(UserEvent.MyReminds)
	public void onMyRemindsHandler(SocketIOClient client,String receiver,AckRequest ack){
		Collection<RemindEvent> events =  remindService.getValidEvents(receiver);
		client.sendEvent(UserEvent.MyReminds, events);
		logger.info(receiver +"'s events was sent "+events);
	}
	

	@OnEvent(UserEvent.InvalidRemind)
	public void onInvalidRemindsHandler(SocketIOClient client,String id,AckRequest ack){
	    remindService.changeOneValid(false, id);
		client.sendEvent(UserEvent.InvalidRemind, id);
		logger.info("events was be invalid with id  "+id);
	}
	

	void updateOnlines(UserIO users) {
		for (User usr : userManager.getUsers()) {
			UserInfo uif = new UserInfo();
			uif.setName(usr.getUsername());
			uif.setLocalName(usr.getLocalName());
			uif.setArgs(usr.getLocalName(), usr.getUsername(), usr.getLocalName(),
					usr.getDepartment().getName());
			uif.setGroup(new NameValuePair(usr.getDepartment().getId(), usr
					.getDepartment().getName()));
			uif.setRole(new NameValuePair(usr.getRole().getId(), usr.getRole()
					.getName()));
			Collection<SecurityRole> ss = usr.getAuthoritys();
			List<NameValuePair> auths = Lists.newArrayList();
			uif.setAuthorties(auths);
			for (SecurityRole ar : ss) {
				auths.add(new NameValuePair(ar.getName(), ar.getDescription()));
			}
			users.getUsers().add(uif);
		}
	}

	UserInfo getUserInfo(String name) {
		UserInfo ui = userCache.get(name);
		if (ui != null) {
			return ui;
		} else {
			ui = userService.getUserInfo(name);
			userCache.put(name, ui);
			return ui;
		}
	}

	@OnEvent(UserEvent.ZtreeNode)
	public void onZtreeNode(SocketIOClient client, TreeIdInfo treeinfo,
			AckRequest ackRequest) {

		String treeNodeId = treeinfo.getTreeId();
		
		Collection<ZTreeNode> jsNodes = getZTreeNodes(treeNodeId);

		client.sendEvent(UserEvent.ZtreeNode, new TreeInfo(treeinfo.getBaseId(),treeNodeId,treeinfo.getTreeTid(), jsNodes));

	}

	public Collection<ZTreeNode> getZTreeNodes(String parent)
			throws DataAccessException {
		Collection<ZTreeNode> trees = ztreeCache.get(parent);
		if (trees != null)
			return trees;
		Collection<Department> depts = Lists.newArrayList();
		Collection<ZTreeNode> jsNodes = Lists.newArrayList();
		Department bean = new Department(StringUtils.isEmpty(parent) ? "root"
				: parent);
		if ("root".equals(bean.getId())) {
			depts = deptService.getChildren("root");
			jsNodes = (Collections2.transform(depts,
					new DepartmentZtreeFunction()));
		} else {
			Collection<User> users = Lists.newArrayList();
			users = userService.findByDepartment(bean.getId());
			depts = deptService.getChildren(bean.getId());
			Collection<ZTreeNode> jsNode = Collections2.transform(depts,
					new DepartmentZtreeFunction());
			jsNodes.addAll(jsNode);
			jsNode = Collections2.transform(users, new OnlineUserZtreeFunction(
					userManager.getUsers()));
			jsNodes.addAll(jsNode);
		}
		ztreeCache.put(bean.getId(), jsNodes);
		return jsNodes;
	}

}